package com.workingpub.commons.orm.internal;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;

import javax.persistence.InheritanceType;
import javax.persistence.JoinTable;

import org.springframework.util.StringUtils;

import com.workingpub.commons.domain.AssignedIdentifier;
import com.workingpub.commons.domain.CreateTimeable;
import com.workingpub.commons.domain.Identifiable;
import com.workingpub.commons.domain.UnDeletable;
import com.workingpub.commons.orm.SqlBuildEntry;
import com.workingpub.commons.utils.Guid;

public class InsertBuilder extends AbstractSqlBuilder {

	public <T extends Identifiable<?>> InsertBuilder(Class<T> type) {
		super(type);
	}

	@Override
	protected <T extends Identifiable<?>> SqlBuildEntry build(EntityMetaInfo metaInfo, T entity) throws Exception {
		SqlBuildEntry sqlEntry = new SqlBuildEntry();
		// 手动设置Id
		if (null != entity && null == entity.getId() && entity instanceof AssignedIdentifier) {
			((AssignedIdentifier) entity).setId(Guid.newGuid());
		}
		if (entity instanceof CreateTimeable) {
			CreateTimeable business = (CreateTimeable) entity;
			if (null == business.getCreateTime()) {
				business.setCreateTime(Calendar.getInstance());
			}
		}
		if (entity instanceof UnDeletable) {
			UnDeletable business = (UnDeletable) entity;
			if (null != business) {
				business.markedAsAvailable();
			}
		}
		sqlEntry.append("Insert into ").append(metaInfo.getTableName()).append(" (");
		boolean first = true;
		for (String column : metaInfo.getColumnInfos().keySet()) {
			if (first) {
				sqlEntry.append(column);
				first = false;
			} else {
				sqlEntry.append(" ,").append(column);
			}
		}

		// 解决多表继续时，子类无主键Id标识的问题
		if (StringUtils.hasText(metaInfo.getIdname()) && !metaInfo.getColumnInfos().containsKey(metaInfo.getIdname())) {
			sqlEntry.append(" ," + metaInfo.getIdname());
		}

		if (InheritanceType.SINGLE_TABLE.equals(metaInfo.getInheritanceType())) {
			sqlEntry.append(" ,").append(metaInfo.getDiscriminatorColumn());
		}
		sqlEntry.append(") values (");
		first = true;
		for (Entry<String, Object> entry : metaInfo.getColumnInfos().entrySet()) {
			if (first) {
				first = false;
				sqlEntry.append(":" + entry.getKey());
				sqlEntry.putParam(entry.getKey(), convertValue(entry.getValue(), entity));
			} else {
				sqlEntry.append(" ,:" + entry.getKey());
				sqlEntry.putParam(entry.getKey(), convertValue(entry.getValue(), entity));
			}
		}
		if (InheritanceType.SINGLE_TABLE.equals(metaInfo.getInheritanceType())) {
			sqlEntry.append(" ,:" + metaInfo.getDiscriminatorColumn());
			sqlEntry.putParam(metaInfo.getDiscriminatorColumn(), metaInfo.getDiscriminatorValue());
		}

		// 解决多表继续时，子类无主键Id标识的问题
		if (StringUtils.hasText(metaInfo.getIdname()) && !metaInfo.getColumnInfos().containsKey(metaInfo.getIdname())) {
			sqlEntry.append(" ,:" + metaInfo.getIdname());
			sqlEntry.putParam(metaInfo.getIdname(), entity.getId());
		}

		sqlEntry.append(")");
		return sqlEntry;
	}

	@Override
	protected <T extends Identifiable<?>> List<SqlBuildEntry> buildManyToMany(Field field, T entity) throws Exception {
		JoinTable joinTable = field.getAnnotation(JoinTable.class);
		return buildManyToMany(joinTable, field, entity);
	}

	@Override
	protected <T extends Identifiable<?>> List<SqlBuildEntry> buildManyToMany(PropertyDescriptor property, T entity)
			throws Exception {
		JoinTable joinTable = property.getReadMethod().getAnnotation(JoinTable.class);
		return buildManyToMany(joinTable, property, entity);
	}

	private <T extends Identifiable<?>> List<SqlBuildEntry> buildManyToMany(JoinTable joinTable, Object value, T entity)
			throws Exception {
		if (StringUtils.isEmpty(entity.getId())) {
			logger.warn("Entity id is null, build failed.");
			return null;
		}
		String tableName = joinTable.name();
		String joinColumn = joinTable.joinColumns()[0].name();
		String inverseColumn = joinTable.inverseJoinColumns()[0].name();

		Collection<?> values = convertValues(value, entity);
		List<SqlBuildEntry> sqlEntries = null;
		if (null != values && !values.isEmpty()) {
			sqlEntries = new ArrayList<SqlBuildEntry>();
			for (Object entry : values) {
				Identifiable<?> domainEntry = Identifiable.class.cast(entry);
				if (null == domainEntry) {
					continue;
				}
				SqlBuildEntry buildEntry = new SqlBuildEntry();
				buildEntry.append("Insert into " + tableName).append("(" + joinColumn).append(" ," + inverseColumn)
						.append(") values (").append(":" + joinColumn).append(" ,:" + inverseColumn).append(")");
				buildEntry.putParam(joinColumn, entity.getId()).putParam(inverseColumn, domainEntry.getId());
				sqlEntries.add(buildEntry);
			}
		}
		return sqlEntries;
	}

}
